1   /*
2    * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.
8    *
9    * This code is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12   * version 2 for more details (a copy is included in the LICENSE file that
13   * accompanied this code).
14   *
15   * You should have received a copy of the GNU General Public License version
16   * 2 along with this work; if not, write to the Free Software Foundation,
17   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18   *
19   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20   * or visit www.oracle.com if you need additional information or have any
21   * questions.
22   */
23  /*
24   * @test
25   * @bug 4691089 4819436 4942982 5104960 6544471 6627549
26   * @summary Validate ISO 4217 data for Currency class.
27   */
28  
29  /*
30   * ############################################################################
31   *
32   *  ValidateISO4217 is a tool to detect differences between the latest ISO 4217
33   *  data and and Java's currency data which is based on ISO 4217.
34   *  If there is a difference, the following file which includes currency data
35   *  may need to be updated.
36   *      src/share/classes/java/util/CurrencyData.properties
37   *
38   * ############################################################################
39   *
40   * 1) Make a golden-data file.
41   *      From BSi's ISO4217 data (TABLE A1.doc), extract four (or eight, if currency is changing)
42   *      fields and save as ./tablea1.txt.
43   *        <Country code>\t<Currency code>\t<Numeric code>\t<Minor unit>[\t<Cutover Date>\t<new Currency code>\t<new Numeric code>\t<new Minor unit>]
44   *      The Cutover Date is given in SimpleDateFormat's 'yyyy-MM-dd-HH-mm-ss' format in the GMT time zone.
45   *
46   * 2) Compile ValidateISO4217.java
47   *
48   * 3) Execute ValidateISO4217 as follows:
49   *      java ValidateISO4217
50   */
51  
52  import java.io.*;
53  import java.text.*;
54  import java.util.*;
55  
56  public class ValidateISO4217 {
57  
58      static final int ALPHA_NUM = 26;
59  
60      static final byte UNDEFINED = 0;
61      static final byte DEFINED = 1;
62      static final byte SKIPPED = 2;
63  
64      /* input files */
65      static final String datafile = "tablea1.txt";
66  
67      /* alpha2-code table */
68      static byte[] codes = new byte[ALPHA_NUM * ALPHA_NUM];
69  
70      static final String[][] additionalCodes = {
71          /* Defined in ISO 4217 list, but don't have code and minor unit info. */
72          {"AQ", "", "", "0"},    // Antarctica
73  
74          /*
75           * Defined in ISO 4217 list, but don't have code and minor unit info in
76           * it. On the othe hand, both code and minor unit are defined in
77           * .properties file. I don't know why, though.
78           */
79          {"GS", "GBP", "826", "2"},      // South Georgia And The South Sandwich Islands
80  
81          /* Not defined in ISO 4217 list, but defined in .properties file. */
82          {"AX", "EUR", "978", "2"},      // \u00c5LAND ISLANDS
83          {"PS", "ILS", "376", "2"},      // Palestinian Territory, Occupied
84  
85          /* Not defined in ISO 4217 list, but added in ISO 3166 country code list */
86          {"JE", "GBP", "826", "2"},      // Jersey
87          {"GG", "GBP", "826", "2"},      // Guernsey
88          {"IM", "GBP", "826", "2"},      // Isle of Man
89          {"BL", "EUR", "978", "2"},      // Saint Barthelemy
90          {"MF", "EUR", "978", "2"},      // Saint Martin
91      };
92  
93      /* Codes that are obsolete, do not have related country */
94      static final String otherCodes =
95          "ADP-AFA-ATS-AYM-BEF-BGL-BOV-BYB-CLF-CYP-DEM-ESP-FIM-FRF-GRD-GWP-IEP-ITL-LUF-MGF-MTL-MXV-NLG-PTE-RUR-SDD-SIT-SRG-TPE-TRL-VEF-USN-USS-XAG-XAU-XBA-XBB-XBC-XBD-XDR-XFO-XFU-XPD-XPT-XTS-XXX-YUM-ZWN";
96  
97      static boolean err = false;
98  
99      static Set<Currency> testCurrencies = new HashSet<Currency>();
100 
101     public static void main(String[] args) throws Exception {
102         CheckDataVersion.check();
103         test1();
104         test2();
105         getAvailableCurrenciesTest();
106 
107         if (err) {
108             throw new RuntimeException("Failed: Validation ISO 4217 data");
109         }
110     }
111 
112     static void test1() throws Exception {
113 
114         try (FileReader fr = new FileReader(new File(System.getProperty("test.src", "."), datafile));
115              BufferedReader in = new BufferedReader(fr))
116         {
117             String line;
118             SimpleDateFormat format = null;
119 
120             while ((line = in.readLine()) != null) {
121                 if (line.length() == 0 || line.charAt(0) == '#') {
122                     continue;
123                 }
124 
125                 StringTokenizer tokens = new StringTokenizer(line, "\t");
126                 String country = tokens.nextToken();
127                 if (country.length() != 2) {
128                     continue;
129                 }
130 
131                 String currency;
132                 String numeric;
133                 String minorUnit;
134                 int tokensCount = tokens.countTokens();
135                 if (tokensCount < 3) {
136                     currency = "";
137                     numeric = "0";
138                     minorUnit = "0";
139                 } else {
140                     currency = tokens.nextToken();
141                     numeric = tokens.nextToken();
142                     minorUnit = tokens.nextToken();
143                     testCurrencies.add(Currency.getInstance(currency));
144 
145                     // check for the cutover
146                     if (tokensCount > 3) {
147                         if (format == null) {
148                             format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
149                             format.setTimeZone(TimeZone.getTimeZone("GMT"));
150                             format.setLenient(false);
151                         }
152                         if (format.parse(tokens.nextToken()).getTime() <
153                             System.currentTimeMillis()) {
154                             currency = tokens.nextToken();
155                             numeric = tokens.nextToken();
156                             minorUnit = tokens.nextToken();
157                             testCurrencies.add(Currency.getInstance(currency));
158                         }
159                     }
160                 }
161                 int index = toIndex(country);
162                 testCountryCurrency(country, currency, Integer.parseInt(numeric),
163                     Integer.parseInt(minorUnit), index);
164             }
165         }
166 
167         for (int i = 0; i < additionalCodes.length; i++) {
168             int index = toIndex(additionalCodes[i][0]);
169             if (additionalCodes[i][1].length() != 0) {
170                 testCountryCurrency(additionalCodes[i][0], additionalCodes[i][1],
171                     Integer.parseInt(additionalCodes[i][2]),
172                     Integer.parseInt(additionalCodes[i][3]), index);
173                 testCurrencies.add(Currency.getInstance(additionalCodes[i][1]));
174             } else {
175                 codes[index] = SKIPPED;
176             }
177         }
178     }
179 
180     static int toIndex(String s) {
181         return ((s.charAt(0) - 'A') * ALPHA_NUM + s.charAt(1) - 'A');
182     }
183 
184     static void testCountryCurrency(String country, String currencyCode,
185                                 int numericCode, int digits, int index) {
186         if (currencyCode.length() == 0) {
187             return;
188         }
189         testCurrencyDefined(currencyCode, numericCode, digits);
190 
191         Locale loc = new Locale("", country);
192         try {
193             Currency currency = Currency.getInstance(loc);
194             if (!currency.getCurrencyCode().equals(currencyCode)) {
195                 System.err.println("Error: [" + country + ":" +
196                     loc.getDisplayCountry() + "] expected: " + currencyCode +
197                     ", got: " + currency.getCurrencyCode());
198                 err = true;
199             }
200 
201             if (codes[index] != UNDEFINED) {
202                 System.out.println("Warning: [" + country + ":" +
203                     loc.getDisplayCountry() +
204                     "] multiple definitions. currency code=" + currencyCode);
205             }
206             codes[index] = DEFINED;
207         }
208         catch (Exception e) {
209             System.err.println("Error: " + e + ": Country=" + country);
210             err = true;
211         }
212     }
213 
214     static void testCurrencyDefined(String currencyCode, int numericCode, int digits) {
215         try {
216             Currency currency = currency = Currency.getInstance(currencyCode);
217 
218             if (currency.getNumericCode() != numericCode) {
219                 System.err.println("Error: [" + currencyCode + "] expected: " +
220                     numericCode + "; got: " + currency.getNumericCode());
221                 err = true;
222             }
223 
224             if (currency.getDefaultFractionDigits() != digits) {
225                 System.err.println("Error: [" + currencyCode + "] expected: " +
226                     digits + "; got: " + currency.getDefaultFractionDigits());
227                 err = true;
228             }
229         }
230         catch (Exception e) {
231             System.err.println("Error: " + e + ": Currency code=" +
232                 currencyCode);
233             err = true;
234         }
235     }
236 
237     static void test2() {
238         for (int i = 0; i < ALPHA_NUM; i++) {
239             for (int j = 0; j < ALPHA_NUM; j++) {
240                 char[] code = new char[2];
241                 code[0] = (char)('A'+ i);
242                 code[1] = (char)('A'+ j);
243                 String country = new String(code);
244                 boolean ex;
245 
246                 if (codes[toIndex(country)] == UNDEFINED) {
247                     ex = false;
248                     try {
249                         Currency.getInstance(new Locale("", country));
250                     }
251                     catch (IllegalArgumentException e) {
252                         ex = true;
253                     }
254                     if (!ex) {
255                         System.err.println("Error: This should be an undefined code and throw IllegalArgumentException: " +
256                             country);
257                         err = true;
258                     }
259                 } else if (codes[toIndex(country)] == SKIPPED) {
260                     Currency cur = null;
261                     try {
262                         cur = Currency.getInstance(new Locale("", country));
263                     }
264                     catch (Exception e) {
265                         System.err.println("Error: " + e + ": Country=" +
266                             country);
267                         err = true;
268                     }
269                     if (cur != null) {
270                         System.err.println("Error: Currency.getInstance() for an this locale should return null: " +
271                             country);
272                         err = true;
273                     }
274                 }
275             }
276         }
277     }
278 
279     /**
280      * This test depends on test1(), where 'testCurrencies' set is constructed
281      */
282     static void getAvailableCurrenciesTest() {
283         Set<Currency> jreCurrencies = Currency.getAvailableCurrencies();
284 
285         // add otherCodes
286         StringTokenizer st = new StringTokenizer(otherCodes, "-");
287         while (st.hasMoreTokens()) {
288             testCurrencies.add(Currency.getInstance(st.nextToken()));
289         }
290 
291         if (!testCurrencies.containsAll(jreCurrencies)) {
292             System.err.print("Error: getAvailableCurrencies() returned extra currencies than expected: ");
293             jreCurrencies.removeAll(testCurrencies);
294             for (Currency c : jreCurrencies) {
295                 System.err.print(" "+c);
296             }
297             System.err.println();
298             err = true;
299         }
300     }
301 }